use super::position::Position;
use std::fmt::Display;

#[cfg(feature = "handwrite_lexer")]
mod handwrite;

#[cfg(not(feature = "handwrite_lexer"))]
mod toolpest;

#[cfg(feature = "handwrite_lexer")]
pub use handwrite::tokenize;

#[cfg(not(feature = "handwrite_lexer"))]
pub use toolpest::tokenize;

#[derive(Debug)]
pub enum Token {
    Const,
    Int,
    Void,
    If,
    Else,
    While,
    Break,
    Continue,
    Return,
    Eq,
    Neq,
    Le,
    Ge,
    And,
    Or,
    Plus,
    Minus,
    Mul,
    Div,
    Mod,
    Assign,
    Lt,
    Gt,
    Not,
    LParen,
    RParen,
    LBrace,
    RBrace,
    LBrackt,
    RBrackt,
    Comma,
    Semicolon,
    IntegerConst(i64),
    Ident(String),
}

impl Token {
    pub fn get_str(&self) -> &'static str {
        match self {
            Self::Const => "CONST",
            Self::Int => "INT",
            Self::Void => "VOID",
            Self::If => "IF",
            Self::Else => "ELSE",
            Self::While => "WHILE",
            Self::Break => "BREAK",
            Self::Continue => "CONTINUE",
            Self::Return => "RETURN",
            Self::Eq => "EQ",
            Self::Neq => "NEQ",
            Self::Le => "LE",
            Self::Ge => "GE",
            Self::And => "AND",
            Self::Or => "OR",
            Self::Plus => "PLUS",
            Self::Minus => "MINUS",
            Self::Mul => "MUL",
            Self::Div => "DIV",
            Self::Mod => "MOD",
            Self::Assign => "ASSIGN",
            Self::Lt => "LT",
            Self::Gt => "GT",
            Self::Not => "NOT",
            Self::LParen => "L_PAREN",
            Self::RParen => "R_PAREN",
            Self::LBrace => "L_BRACE",
            Self::RBrace => "R_BRACE",
            Self::LBrackt => "L_BRACKT",
            Self::RBrackt => "R_BRACKT",
            Self::Comma => "COMMA",
            Self::Semicolon => "SEMICOLON",
            Self::IntegerConst(_) => "INTEGER_CONST",
            Self::Ident(_) => "IDENT",
        }
    }

    pub fn get_value(&self) -> String {
        match self {
            Self::Const => "const".into(),
            Self::Int => "int".into(),
            Self::Void => "void".into(),
            Self::If => "if".into(),
            Self::Else => "else".into(),
            Self::While => "while".into(),
            Self::Break => "break".into(),
            Self::Continue => "continue".into(),
            Self::Return => "return".into(),
            Self::Eq => "==".into(),
            Self::Neq => "!=".into(),
            Self::Le => "<=".into(),
            Self::Ge => ">=".into(),
            Self::And => "&&".into(),
            Self::Or => "||".into(),
            Self::Plus => "+".into(),
            Self::Minus => "-".into(),
            Self::Mul => "*".into(),
            Self::Div => "/".into(),
            Self::Mod => "%".into(),
            Self::Assign => "=".into(),
            Self::Lt => "<".into(),
            Self::Gt => ">".into(),
            Self::Not => "!".into(),
            Self::LParen => "(".into(),
            Self::RParen => ")".into(),
            Self::LBrace => "{".into(),
            Self::RBrace => "}".into(),
            Self::LBrackt => "[".into(),
            Self::RBrackt => "]".into(),
            Self::Comma => ",".into(),
            Self::Semicolon => ";".into(),
            Self::IntegerConst(num) => num.to_string(),
            Self::Ident(ident) => ident.clone(),
        }
    }
}

#[derive(Debug)]
pub struct TokenWrap {
    token: Token,
    position: Position,
}

impl TokenWrap {
    fn new(token: Token, position: Position) -> Self {
        Self { token, position }
    }
}

impl Display for TokenWrap {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let token_str = self.token.get_str();
        let token_value = self.token.get_value();
        write!(
            f,
            "{} {} at Line {}.",
            token_str, token_value, self.position.line
        )
    }
}

pub struct LexerError {
    pub message: String,
    pub position: Position,
}

impl Display for LexerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Error type A at Line {}: {} {}",
            self.position.line, self.position.col, self.message
        )
    }
}
